#include "pch.h"
#include "RendererDX11.h"
#include "DirectXHelper.h"
#include "math/Vector2f.h"

using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::UI::Core;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;

namespace Renderer
{
	RendererDX11::RendererDX11(CoreWindow^ window)
					:Renderer(Math::Vector2f(window->Bounds.Width, window->Bounds.Height))
	{

	}

	RendererDX11::~RendererDX11()
	{

	}

	void RendererDX11::DoInit()
	{

	}


	// Initialize the Direct3D resources required to run.
	void RendererDX11::Initialize(CoreWindow^ window)
	{
		m_window = window;

		CreateDeviceResources();
		CreateWindowSizeDependentResources();
	}

	// Recreate all device resources and set them back to the current state.
	void RendererDX11::HandleDeviceLost()
	{
		// Reset these member variables to ensure that UpdateForWindowSizeChange recreates all resources.
		m_windowBounds.Width = 0;
		m_windowBounds.Height = 0;
		m_swapChain = nullptr;

		CreateDeviceResources();
		UpdateForWindowSizeChange();
	}

	// These are the resources that depend on the device.
	void RendererDX11::CreateDeviceResources()
	{
		// This flag adds support for surfaces with a different color channel ordering
		// than the API default. It is required for compatibility with Direct2D.
		UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
		// If the project is in a debug build, enable debugging via SDK Layers with this flag.
		creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

		// This array defines the set of DirectX hardware feature levels this app will support.
		// Note the ordering should be preserved.
		// Don't forget to declare your application's minimum required feature level in its
		// description.  All applications are assumed to support 9.1 unless otherwise stated.
		D3D_FEATURE_LEVEL featureLevels[] = 
		{
			D3D_FEATURE_LEVEL_11_1,
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
			D3D_FEATURE_LEVEL_9_3,
			D3D_FEATURE_LEVEL_9_2,
			D3D_FEATURE_LEVEL_9_1
		};

		// Create the Direct3D 11 API device object and a corresponding context.
		ComPtr<ID3D11Device> device;
		ComPtr<ID3D11DeviceContext> context;
		DX::ThrowIfFailed(
			D3D11CreateDevice(
			nullptr, // Specify nullptr to use the default adapter.
			D3D_DRIVER_TYPE_HARDWARE,
			nullptr,
			creationFlags, // Set set debug and Direct2D compatibility flags.
			featureLevels, // List of feature levels this app can support.
			ARRAYSIZE(featureLevels),
			D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
			&device, // Returns the Direct3D device created.
			&m_featureLevel, // Returns feature level of device created.
			&context // Returns the device immediate context.
			)
			);

		// Get the Direct3D 11.1 API device and context interfaces.
		DX::ThrowIfFailed(
			device.As(&m_d3dDevice)
			);

		DX::ThrowIfFailed(
			context.As(&m_d3dContext)
			);
	}

	// Allocate all memory resources that change on a window SizeChanged event.
	void RendererDX11::CreateWindowSizeDependentResources()
	{ 
		// Store the window bounds so the next time we get a SizeChanged event we can
		// avoid rebuilding everything if the size is identical.
		m_windowBounds = m_window->Bounds;

		// Calculate the necessary swap chain and render target size in pixels.
		float windowWidth = ConvertDipsToPixels(m_windowBounds.Width);
		float windowHeight = ConvertDipsToPixels(m_windowBounds.Height);

		// The width and height of the swap chain must be based on the window's
		// landscape-oriented width and height. If the window is in a portrait
		// orientation, the dimensions must be reversed.
		m_orientation = DisplayProperties::CurrentOrientation;
		bool swapDimensions =
			m_orientation == DisplayOrientations::Portrait ||
			m_orientation == DisplayOrientations::PortraitFlipped;
		m_renderTargetSize.Width = swapDimensions ? windowHeight : windowWidth;
		m_renderTargetSize.Height = swapDimensions ? windowWidth : windowHeight;

		if(m_swapChain != nullptr)
		{
			// If the swap chain already exists, resize it.
			DX::ThrowIfFailed(
				m_swapChain->ResizeBuffers(
				2, // Double-buffered swap chain.
				static_cast<UINT>(m_renderTargetSize.Width),
				static_cast<UINT>(m_renderTargetSize.Height),
				DXGI_FORMAT_B8G8R8A8_UNORM,
				0
				)
				);
		}
		else
		{
			// Otherwise, create a new one using the same adapter as the existing Direct3D device.
			DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
			swapChainDesc.Width = static_cast<UINT>(m_renderTargetSize.Width); // Match the size of the window.
			swapChainDesc.Height = static_cast<UINT>(m_renderTargetSize.Height);
			swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
			swapChainDesc.Stereo = false;
			swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
			swapChainDesc.SampleDesc.Quality = 0;
			swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
			swapChainDesc.Scaling = DXGI_SCALING_NONE;
			swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
			swapChainDesc.Flags = 0;

			ComPtr<IDXGIDevice1>  dxgiDevice;
			DX::ThrowIfFailed(
				m_d3dDevice.As(&dxgiDevice)
				);

			ComPtr<IDXGIAdapter> dxgiAdapter;
			DX::ThrowIfFailed(
				dxgiDevice->GetAdapter(&dxgiAdapter)
				);

			ComPtr<IDXGIFactory2> dxgiFactory;
			DX::ThrowIfFailed(
				dxgiAdapter->GetParent(
				__uuidof(IDXGIFactory2), 
				&dxgiFactory
				)
				);

			Windows::UI::Core::CoreWindow^ window = m_window.Get();
			DX::ThrowIfFailed(
				dxgiFactory->CreateSwapChainForCoreWindow(
				m_d3dDevice.Get(),
				reinterpret_cast<IUnknown*>(window),
				&swapChainDesc,
				nullptr, // Allow on all displays.
				&m_swapChain
				)
				);

			// Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
			// ensures that the application will only render after each VSync, minimizing power consumption.
			DX::ThrowIfFailed(
				dxgiDevice->SetMaximumFrameLatency(1)
				);
		}

		// Set the proper orientation for the swap chain, and generate the
		// 3D matrix transformation for rendering to the rotated swap chain.
		DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
		switch (m_orientation)
		{
		case DisplayOrientations::Landscape:
			rotation = DXGI_MODE_ROTATION_IDENTITY;
			m_orientationTransform3D = XMFLOAT4X4( // 0-degree Z-rotation
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::Portrait:
			rotation = DXGI_MODE_ROTATION_ROTATE270;
			m_orientationTransform3D = XMFLOAT4X4( // 90-degree Z-rotation
				0.0f, 1.0f, 0.0f, 0.0f,
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::LandscapeFlipped:
			rotation = DXGI_MODE_ROTATION_ROTATE180;
			m_orientationTransform3D = XMFLOAT4X4( // 180-degree Z-rotation
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, -1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::PortraitFlipped:
			rotation = DXGI_MODE_ROTATION_ROTATE90;
			m_orientationTransform3D = XMFLOAT4X4( // 270-degree Z-rotation
				0.0f, -1.0f, 0.0f, 0.0f,
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		default:
			throw ref new Platform::FailureException();
		}

		DX::ThrowIfFailed(
			m_swapChain->SetRotation(rotation)
			);

		// Create a render target view of the swap chain back buffer.
		ComPtr<ID3D11Texture2D> backBuffer;
		DX::ThrowIfFailed(
			m_swapChain->GetBuffer(
			0,
			__uuidof(ID3D11Texture2D),
			&backBuffer
			)
			);

		DX::ThrowIfFailed(
			m_d3dDevice->CreateRenderTargetView(
			backBuffer.Get(),
			nullptr,
			&m_renderTargetView
			)
			);

		// Create a depth stencil view.
		CD3D11_TEXTURE2D_DESC depthStencilDesc(
			DXGI_FORMAT_D24_UNORM_S8_UINT, 
			static_cast<UINT>(m_renderTargetSize.Width),
			static_cast<UINT>(m_renderTargetSize.Height),
			1,
			1,
			D3D11_BIND_DEPTH_STENCIL
			);

		ComPtr<ID3D11Texture2D> depthStencil;
		DX::ThrowIfFailed(
			m_d3dDevice->CreateTexture2D(
			&depthStencilDesc,
			nullptr,
			&depthStencil
			)
			);

		CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
		DX::ThrowIfFailed(
			m_d3dDevice->CreateDepthStencilView(
			depthStencil.Get(),
			&depthStencilViewDesc,
			&m_depthStencilView
			)
			);

		// Set the rendering viewport to target the entire window.
		CD3D11_VIEWPORT viewport(
			0.0f,
			0.0f,
			m_renderTargetSize.Width,
			m_renderTargetSize.Height
			);

		m_d3dContext->RSSetViewports(1, &viewport);
	}

	// This method is called in the event handler for the SizeChanged event.
	void RendererDX11::UpdateForWindowSizeChange()
	{
		if (m_window->Bounds.Width  != m_windowBounds.Width ||
			m_window->Bounds.Height != m_windowBounds.Height ||
			m_orientation != DisplayProperties::CurrentOrientation)
		{
			ID3D11RenderTargetView* nullViews[] = {nullptr};
			m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
			m_renderTargetView = nullptr;
			m_depthStencilView = nullptr;
			m_d3dContext->Flush();
			CreateWindowSizeDependentResources();
		}
	}

	// Method to deliver the final image to the display.
	void RendererDX11::Present()
	{
		// The application may optionally specify "dirty" or "scroll"
		// rects to improve efficiency in certain scenarios.
		DXGI_PRESENT_PARAMETERS parameters = {0};
		parameters.DirtyRectsCount = 0;
		parameters.pDirtyRects = nullptr;
		parameters.pScrollRect = nullptr;
		parameters.pScrollOffset = nullptr;

		// The first argument instructs DXGI to block until VSync, putting the application
		// to sleep until the next VSync. This ensures we don't waste any cycles rendering
		// frames that will never be displayed to the screen.
		HRESULT hr = m_swapChain->Present1(1, 0, &parameters);

		// Discard the contents of the render target.
		// This is a valid operation only when the existing contents will be entirely
		// overwritten. If dirty or scroll rects are used, this call should be removed.
		m_d3dContext->DiscardView(m_renderTargetView.Get());

		// Discard the contents of the depth stencil.
		m_d3dContext->DiscardView(m_depthStencilView.Get());

		// If the device was removed either by a disconnect or a driver upgrade, we 
		// must recreate all device resources.
		if (hr == DXGI_ERROR_DEVICE_REMOVED)
		{
			HandleDeviceLost();
		}
		else
		{
			DX::ThrowIfFailed(hr);
		}
	}

	// Method to convert a length in device-independent pixels (DIPs) to a length in physical pixels.
	float RendererDX11::ConvertDipsToPixels(float dips)
	{
		static const float dipsPerInch = 96.0f;
		return floor(dips * DisplayProperties::LogicalDpi / dipsPerInch + 0.5f); // Round to nearest integer.
	}

}